package cc.rengu.oltp.utility.util;

import cc.rengu.oltp.utility.annotation.FIELDPATH;
import com.alibaba.fastjson.JSON;
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;

import java.io.Serializable;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * 对象操作自定义类
 * Created by 王成 on 2018/10/23.
 */
public class BeanUtil implements Serializable {
    /**
     * 将源对象中的值拷贝到目标对象中(BigDecimal转String时会将金额转换成分再转换)
     *
     * @param srcBean  源对象
     * @param destBean 目标对象
     * @throws Exception 异常
     */
    public static void beanCopy(Object srcBean, Object destBean) {
        Field[] destBeanFields = destBean.getClass().getDeclaredFields();
        for (Field field : destBeanFields) {
            try {
                /* 根据目标成员名获取源变量名 */
                FIELDPATH fieldpath = field.getAnnotation(FIELDPATH.class);
                String srcFieldName = (null == fieldpath) ? field.getName() : fieldpath.value();
                if ("serialVersionUID".equals(srcFieldName)) {
                    continue;
                }
                Optional<Field> srcFieldOpt = Arrays.stream(srcBean.getClass().getDeclaredFields()).filter(item -> item.getName().equals(srcFieldName)).findFirst();
                if (!srcFieldOpt.isPresent()) {
                    srcFieldOpt = Arrays.stream(srcBean.getClass().getSuperclass().getDeclaredFields()).filter(item -> item.getName().equals(srcFieldName)).findFirst();
                    if (!srcFieldOpt.isPresent()) {
                        continue;
                    }
                }
                Field srcField = srcFieldOpt.get();
                srcField.setAccessible(true);
                Object srcObject = srcField.get(srcBean);
                if (null == srcObject) {
                    continue;
                }
                /* 根据目标成员名的变量类型及方法进行赋值 */
                String methodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
                Method method = destBean.getClass().getDeclaredMethod(methodName, field.getType());
                field.setAccessible(true);
                Type type = field.getGenericType();
                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    if (parameterizedType.getRawType().equals(List.class)) {
                        if (isBaseDataType(parameterizedType.getActualTypeArguments()[0])) {
                            if (srcField.getGenericType().equals(BigDecimal.class) && type.equals(String.class)) {
                                /* 数据源字段类型为BigDecimal，目标为非BigDecimal时，转换时将源字段值按金额转换成以为单位的值 */
                                String objectString = AmountUtil.changeY2F(JSON.toJSONString(srcObject));
                                method.invoke(destBean, JSON.parseObject(objectString, field.getType()));
                            } else {
                                String objectString = JSON.toJSONString(srcObject);
                                method.invoke(destBean, JSON.parseObject(objectString, field.getType()));
                            }
                        } else {
                            List<Object> objectList = new ArrayList<>();
                            for (int i = 0; i < ((List) srcObject).size(); i++) {
                                Object destObject = ((Class) parameterizedType.getActualTypeArguments()[0]).newInstance();
                                beanCopy(((List) srcObject).get(i), destObject);
                                objectList.add(destObject);
                            }
                            method.invoke(destBean, objectList);
                        }
                    } else {
                        beanCopy(srcObject, field.get(destBean));
                    }
                } else if (isBaseDataType(type)) {
                    if (srcField.getGenericType().equals(BigDecimal.class) && type.equals(String.class)) {
                        /* 数据源字段类型为BigDecimal，目标为String时，转换时将源字段值按金额转换成以为单位的值 */
                        String objectString = AmountUtil.changeY2F(JSON.toJSONString(srcObject));
                        method.invoke(destBean, JSON.parseObject(objectString, field.getType()));
                    } else {
                        String objectString = JSON.toJSONString(srcObject);
                        method.invoke(destBean, JSON.parseObject(objectString, field.getType()));
                    }
                } else {
                    Object destObject = ((Class) type).newInstance();
                    beanCopy(srcObject, destObject);
                    method.invoke(destBean, destObject);
                }
            } catch (Exception e) {

                continue;
            }
        }
    }

    /**
     * 判断是否为基本数据类型
     *
     * @param BaseDataType 基础数据类型
     * @return true-是，false-否
     */
    private static boolean isBaseDataType(Type BaseDataType) {
        return BaseDataType.equals(String.class) || BaseDataType.equals(Date.class) || BaseDataType.equals(DateTimeLiteralExpression.DateTime.class) ||
                BaseDataType.equals(Integer.class) || BaseDataType.equals(BigInteger.class) || "int".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Byte.class) || "byte".equals(BaseDataType.getTypeName()) || "byte[]".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(BigDecimal.class) || BaseDataType.equals(Boolean.class) || "boolean".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Long.class) || "long".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Double.class) || "double".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Float.class) || "float".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Character.class) || "char".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Short.class) || "short".equals(BaseDataType.getTypeName());
    }

    /**
     * 将目标对象中为空的字段从原对象中取值
     *
     * @param srcBean  原对象
     * @param destBean 目标对对象
     * @throws Exception 异常
     */
    public static void nullValueCopy(Object srcBean, Object destBean) throws Exception {
        Field[] destBeanFields = destBean.getClass().getDeclaredFields();
        for (Field field : destBeanFields) {
            FIELDPATH fieldpath = field.getAnnotation(FIELDPATH.class);
            String srcFieldName = (null == fieldpath) ? field.getName() : fieldpath.value();
            Optional<Field> srcFieldOpt = Arrays.stream(srcBean.getClass().getDeclaredFields()).filter(item -> item.getName().equals(srcFieldName)).findFirst();
            if (!srcFieldOpt.isPresent()) {
                continue;
            }
            Field srcField = srcBean.getClass().getDeclaredField(srcFieldName);
            srcField.setAccessible(true);
            String methodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
            Method method = destBean.getClass().getDeclaredMethod(methodName, field.getType());
            field.setAccessible(true);
            String fieldTypeName = field.getType().getSimpleName();
            if (null == field.get(destBean)) {
                /* 判断源数据值是否为空 */
                if (null != srcField.get(srcBean)) {
                    method.invoke(destBean, srcField.get(srcBean));
                }
            } else if (null != srcField.get(srcBean)) {
                switch (fieldTypeName) {
                    case "int":
                    case "Integer":
                        String destValue = String.valueOf(field.get(destBean));
                        if ("0".equals(destValue) && !destValue.equals(srcField.get(srcBean))) {
                            method.invoke(destBean, srcField.get(srcBean));
                        }
                        break;
                    case "long":
                    case "Long":
                    case "short":
                    case "Short":
                    case "double":
                    case "Double":
                    case "float":
                    case "Float":
                    case "char":
                    case "Character":
                        break;
                    case "String":
                    case "Object":
                        break;
                    case "boolean":
                    case "Boolean":
                        break;
                    case "byte":
                    case "byte[]":
                        break;
                    case "Date":
                        break;
                    case "List":
                        break;
                    default:
                        break;
                }
            }
        }
    }
}
